package org.springrain.javadoc4openapi;

import com.thoughtworks.qdox.model.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * java源代码的工具类
 *
 * @author springrain
 * @version 0.0.1
 * @since 0.0.1
 */
public class OpenAPIUtils {


    //表示参数的注释,例如方法上的注释 @param name 姓名
    private static final String PARAM = "param";

    // 默认的请求协议
    private static final String defaultConsume = "application/x-www-form-urlencoded";
    //后台开发专用,用于处理application/x-www-form-urlencoded时属性在swaggerui里展开了,开发模式会显示实体类
   // private static final String devConsume = "后台开发专用,前台使用application/x-www-form-urlencoded";


    private OpenAPIUtils() {
    }


    /**
     * 根据JavaClass信息,添加path和component
     *
     * @param controllerClass      SpringMVC 的controller
     * @param pathsMap             openapi的paths对象Map
     * @param componentsSchemasMap openapi的components-Schemas定义Map
     */
    static void addPathComponentSchema(JavaClass controllerClass, Map<String, Object> pathsMap, Map<String, Object> componentsSchemasMap) {

        //非空判断
        if (controllerClass == null || pathsMap == null || componentsSchemasMap == null) {
            return;
        }

        //获取controller的注解,如果没有注解就不处理
        List<JavaAnnotation> javaAnnotations = controllerClass.getAnnotations();
        if (CollectionUtils.isEmpty(javaAnnotations)) {
            return;
        }
        //controller配置的uri和请求方法,用StringBuffer是为了对象传递,变相实现两个返回值
        StringBuffer controllerURI = new StringBuffer("");
        StringBuffer controllerMethod = new StringBuffer("");
        //取值注解中的uri和请求方法,并赋值
        wrapRequestMappingAnnotation(javaAnnotations, controllerURI, controllerMethod);

        // 获取Controller所有的方法,处理每个方法映射的路径
        List<JavaMethod> methods = controllerClass.getMethods();

        //循环每个映射的方法
        for (JavaMethod javaMethod : methods) {


            //获取方法所有的注解,用于获取方法映射的路径
            List<JavaAnnotation> methodAnnotations = javaMethod.getAnnotations();
            if (CollectionUtils.isEmpty(methodAnnotations)) {
                continue;
            }
            //方法映射的请求路径和方法,用StringBuffer是为了对象传递,变相实现两个返回值
            StringBuffer uriSB = new StringBuffer("");
            StringBuffer methodSB = new StringBuffer("");
            //获取方法映射的路径和方法,并赋值
            wrapRequestMappingAnnotation(methodAnnotations, uriSB, methodSB);
            String uri = uriSB.toString();
            String method = methodSB.toString();
            //如果没用映射路径,内部方法,不提供API接口
            if (StringUtils.isBlank(uri)) {
                continue;
            }

            //如果没有请求方法,默认使用Controller配置的方法.
            if (StringUtils.isBlank(method)) {
                method = controllerMethod.toString();
            }

            //方法请求的完整路径
            String path = controllerURI.toString() + uri;
            // openapi路径和方法信息的map: paths-uri-get 的Map
            Map<String, Object> pathsURIMap = new HashMap<>();


            // paths-uri-get--object openapi基本信息的map
            Map<String, Object> pathsURIMethodMap = new HashMap<>();

            //方法的tag,就是类名,和tags的值对应,这样每个controller就是一个折叠了.
            //一个类下,每个方法的 tags 是相同的类名,所以不用放到循环外声明,方便阅读
            List<String> pathsURIMethodTagsList = new ArrayList<>();
            pathsURIMethodTagsList.add(controllerClass.getSimpleName());
            //添加方法的tags,用于同一个类的折叠
            pathsURIMethodMap.put("tags", pathsURIMethodTagsList);

            //包装paths-uri-get--object的openapi信息,如果有对象定义,就添加到componentsSchemasMap
            wrapPathsURIMethodMap(javaMethod, pathsURIMethodMap, componentsSchemasMap);

            //如果没有指定请求方法,默认GET,POST请求
            if (StringUtils.isBlank(method)) {
                pathsURIMap.put("get", pathsURIMethodMap);
                pathsURIMap.put("post", pathsURIMethodMap);
            } else {//如果指定了请求方法
                pathsURIMap.put(method, pathsURIMethodMap);
            }

            //设置path路径,对应的信息. get--object
            pathsMap.put(path, pathsURIMap);

        }


    }

    /**
     * 根据注解获取映射的路径和请求方法
     *
     * @param javaAnnotation 注解列表
     * @param uri            请求路径,StringBuffer对象,变相返回值
     * @param method         请求方法,StringBuffer对象,变相返回值
     */
    private static void wrapRequestMappingAnnotation(List<JavaAnnotation> javaAnnotation, StringBuffer uri, StringBuffer method) {

        String s_uri = null;
        String s_method = null;

        //循环所有的注解
        for (JavaAnnotation annotation : javaAnnotation) {
            //获取注解的类型
            String annotationType = annotation.getType().toString();
            //获取注解的值
            Object o_value = annotation.getNamedParameter("value");
            String s_value = "";
            if (o_value != null) {
                s_value = o_value.toString();
                //默认带 " 双引号,替换掉
                s_value = s_value.replaceAll("\"", "");
            }

            //如果是@RequestMapping注解,获取方法method  get-post
            if ("org.springframework.web.bind.annotation.RequestMapping".equals(annotationType)) {
                //赋值给uri
                s_uri = s_value;
                //获取注解中配置的请求方法
                Object o_method = annotation.getNamedParameter("method");
                //如果有值
                if (o_method != null) {
                    // RequestMethod.POST ,获取方法名小写, get-post
                    String[] split = o_method.toString().split("\\.");
                    String s = split[split.length - 1];
                    s_method = s.toLowerCase();
                }

            } else if ("org.springframework.web.bind.annotation.PostMapping".equals(annotationType)) { //如果是 @PostMapping注解
                s_uri = s_value;
                s_method = "post";
            } else if ("org.springframework.web.bind.annotation.GetMapping".equals(annotationType)) { //如果是 @GetMapping注解
                s_uri = s_value;
                s_method = "get";
            } else if ("org.springframework.web.bind.annotation.PutMapping".equals(annotationType)) { //如果是 @PutMapping注解
                s_uri = s_value;
                s_method = "put";
            } else if ("org.springframework.web.bind.annotation.DeleteMapping".equals(annotationType)) { //如果是 @DeleteMapping注解
                s_uri = s_value;
                s_method = "delete";
            } else {
                continue;
            }

        }
        if (StringUtils.isNotBlank(s_uri)) {
            uri.append(s_uri);
        }
        if (StringUtils.isNotBlank(s_method)) {
            method.append(s_method);
        }


    }

    /**
     * 包装paths-uri-get--object的openapi信息,如果有对象定义,就添加到componentsSchemasMap
     *
     * @param javaMethod           qdox的java方法类
     * @param pathsURIMethodMap    openapi需要的method信息
     * @param componentsSchemasMap 对象定义的Map
     */
    private static void wrapPathsURIMethodMap(JavaMethod javaMethod, Map<String, Object> pathsURIMethodMap, Map<String, Object> componentsSchemasMap) {
        //获取方法的注释
        String javaMethodComment = javaMethod.getComment();
        //如果有注释,就设置到方法的说明里
        if (StringUtils.isNotBlank(javaMethodComment)) {
            pathsURIMethodMap.put("summary", javaMethodComment);
            pathsURIMethodMap.put("description", javaMethodComment);
        }

        //封装 请求方法的 paths-uri-get--parameters 参数列表
        wrapPathsURIMethodParametersList(javaMethod, pathsURIMethodMap, componentsSchemasMap);

        //responses paths-uri-get-responses
        Map<String, Object> pathsURIMethodResponsesMap = new HashMap<>();
        pathsURIMethodMap.put("responses", pathsURIMethodResponsesMap);
        wrapPathsURIMethodResponsesMap(javaMethod, pathsURIMethodResponsesMap, componentsSchemasMap);


    }


    /**
     * 封装java方法参数信息
     *
     * @param javaMethod           qdox的java方法类
     * @param pathsURIMethodMap    openapi需要的method信息,用于根据参数设置produces
     * @param componentsSchemasMap 对象定义的Map
     */
    private static void wrapPathsURIMethodParametersList(JavaMethod javaMethod, Map<String, Object> pathsURIMethodMap, Map<String, Object> componentsSchemasMap) {

        // java方法的参数
        List<JavaParameter> methodParameters = javaMethod.getParameters();
        //参数为空
        if (CollectionUtils.isEmpty(methodParameters)) {
            return;
        }


        //声明默认的请求协议
        String consume = defaultConsume;


        // 请求方法的 paths-uri-get--parameters 参数列表,只放入 path,query,header类型的参数
        List<Map<String, Object>> pathsURIMethodParametersList = new ArrayList<>();
        //最后再添加,pathsURIMethodParametersList不为空的时候
       // pathsURIMethodMap.put("parameters", pathsURIMethodParametersList);

        // object 或者array等复杂对象,放到requestBody中
        //requestBody的嵌套逻辑:requestBody-content-application/json-schema-[properties]-name-object
        Map<String, Object> requestBodyMap = new HashMap<>();
        //设置requestBody
        pathsURIMethodMap.put("requestBody", requestBodyMap);

        //requestBody-content的map
        Map<String, Object> requestBodyContentMap = new HashMap<>();
        requestBodyMap.put("content", requestBodyContentMap);

        Map<String, Object> requestBodyContentConsumeMap = new HashMap<>();
        //需要确定了 consume 才能设置
        //requestBodyContentMap.put(consume,requestBodyContentConsumeMap);

        // requestBody-content-application/json-schema
        Map<String, Object> requestBodyContentConsumeSchemaMap = new HashMap<>();
        requestBodyContentConsumeMap.put("schema", requestBodyContentConsumeSchemaMap);

        // 使用allOf关联多个$ref复杂对象,处理mvc多个对象接收参数.
        // requestBody-content-application/json-schema-allOf
        List<Map<String, Object>> requestBodyContentConsumeSchemaAllOfList = new ArrayList<>();
        //如果不为空,才添加 allOf
        //requestBodyContentConsumeSchemaMap.put("allOf", requestBodyContentConsumeSchemaAllOfList);

        //属性的map requestBody-content-application/json-schema-properties
        Map<String, Object> requestBodyContentConsumeSchemaPropertiesMap = new HashMap<>();
        //最后再添加,requestBodyContentConsumeSchemaPropertiesMap不为空的时候
       // requestBodyContentConsumeSchemaMap.put("properties", requestBodyContentConsumeSchemaPropertiesMap);

        //不能为空的字段,用于记录properties中不能为空的字段
        List<String> requestBodyContentConsumeSchemaRequiredList = new ArrayList<>();


        // 读取java方法参数的注释,@param注解 key是字段名称,value是注释的值,return和throws为key,值是后面的注释
        Map<String, String> methodParamCommentMap = readJavaMethodParamComment(javaMethod);

        //循环controller方法的参数,用于获取参数类型以及注解,获取正确的请求协议consume.
        for (JavaParameter controllerMethodParameter : methodParameters) {
            //获取参数类型的名称,例如 java.lang.String
            String parameterTypeName = controllerMethodParameter.getType().getFullyQualifiedName();
            //忽略的参数类型
            if (GenericClassUtils.isIgnoreParameterJavaType(parameterTypeName)) {
                continue;
            }

            //参数名称
            String parameterName = controllerMethodParameter.getName();

            // controller的方法参数信息
            // 可以用在 paths-uri-get--parameters 或者 requestBody-content-application/json-schema-properties
            Map<String, Object> controllerMethodParameterInfoMap = new HashMap<>();
            //获取参数的注释
            if (methodParamCommentMap.get(parameterName) != null) {
                controllerMethodParameterInfoMap.put("description", methodParamCommentMap.get(parameterName));
            }


            // 参数的类型
            String in = "";
            //默认不必填
            boolean required = false;
            //默认类型
            String type = "object";
            String format = "object";

            //获取java基础类型的对照
            String[] typeValue = GenericClassUtils.getJavaBaseType(parameterTypeName);
            if (typeValue != null) {
                type = typeValue[0];
                format = typeValue[1];
            }

            //如果是文件上传类型
            if ("binary".equalsIgnoreCase(format)) {
                //consume = "multipart/form-data";
                consume = "application/octet-stream";
            }

            //controller方法参数的注解
            List<JavaAnnotation> controllerMethodParameterAnnotations = controllerMethodParameter.getAnnotations();
            // 循环controller方法参数的注解
            for (JavaAnnotation controllerMethodParameterAnnotation : controllerMethodParameterAnnotations) {
                //获取注解的类型名称,例如java.lang.String
                String annotationType = controllerMethodParameterAnnotation.getType().toString();

                if ("org.springframework.web.bind.annotation.RequestParam".equals(annotationType)) { // @RequestParam注解参数
                    in = "formData";
                } else if ("org.springframework.web.bind.annotation.RequestPart".equals(annotationType)) {// @RequestPart注解参数
                    in = "formData";
                } else if ("org.springframework.web.bind.annotation.PathVariable".equals(annotationType)) {// @PathVariable注解参数
                    in = "path";
                } else if ("org.springframework.web.bind.annotation.RequestBody".equals(annotationType)) {// @RequestBody注解参数
                    consume = "application/json";
                    in = "body";
                } else if ("org.springframework.web.bind.annotation.RequestHeader".equals(annotationType)) {// @RequestHeader注解参数
                    in = "header";
                } else if ("org.springframework.web.bind.annotation.CookieValue".equals(annotationType)) {// @CookieValue注解参数
                    in = "header";
                } else {
                    continue;
                }

                //是否必填
                Object o_required = controllerMethodParameterAnnotation.getNamedParameter("required");
                if (o_required == null) {//为空就默认必填
                    required = true;
                } else {
                    //去掉 " 双号
                    String s_required = o_required.toString().replaceAll("\"", "");
                    required = Boolean.valueOf(s_required);
                }

                break;

            }


            // 如果是path或者header参数,放到 paths-uri-get--parameters
            if ("path".equalsIgnoreCase(in) || "header".equalsIgnoreCase(in)) {
                controllerMethodParameterInfoMap.put("name", parameterName);
                controllerMethodParameterInfoMap.put("in", in);
                controllerMethodParameterInfoMap.put("required", required);

                Map<String, String> methodParameterSchemaMap = new HashMap<>();
                methodParameterSchemaMap.put("type", type);
                methodParameterSchemaMap.put("format", format);
                controllerMethodParameterInfoMap.put("schema", methodParameterSchemaMap);


                //添加参数,放到 paths-uri-get--parameters下
                pathsURIMethodParametersList.add(controllerMethodParameterInfoMap);


            } else if ("array".equalsIgnoreCase(type) || "object".equalsIgnoreCase(type)) { //如果是复杂类型,放到requestBody-content-application/json-schema-allOf下
                controllerMethodParameterInfoMap.put("type", type);
                controllerMethodParameterInfoMap.put("format", format);
                //包装复杂类型的信息,放到allOf下
                GenericClassUtils.wrapSchemaRefObjectMap(controllerMethodParameter.getType(), controllerMethodParameterInfoMap, componentsSchemasMap);
                requestBodyContentConsumeSchemaAllOfList.add(controllerMethodParameterInfoMap);

            } else { // 普通类型放到 requestBody-content-application/json-schema-properties
                controllerMethodParameterInfoMap.put("type", type);
                controllerMethodParameterInfoMap.put("format", format);
                //设置属性,name为key
                requestBodyContentConsumeSchemaPropertiesMap.put(parameterName, controllerMethodParameterInfoMap);
                if (required) {//如果不能为空
                    requestBodyContentConsumeSchemaRequiredList.add(parameterName);
                }

            }

        }

        //如果属性不为空
        if (requestBodyContentConsumeSchemaPropertiesMap.size()>0) {
            requestBodyContentConsumeSchemaMap.put("properties", requestBodyContentConsumeSchemaPropertiesMap);
        }

        //如果参数不为空
        if (CollectionUtils.isNotEmpty(pathsURIMethodParametersList)) {
            pathsURIMethodMap.put("parameters", pathsURIMethodParametersList);
        }
        //如果存在非空的属性
        if (CollectionUtils.isNotEmpty(requestBodyContentConsumeSchemaRequiredList)) {
            requestBodyContentConsumeSchemaMap.put("required", requestBodyContentConsumeSchemaRequiredList);
        }

        // 如果有allOf属性
        if (CollectionUtils.isNotEmpty(requestBodyContentConsumeSchemaAllOfList)) {
            requestBodyContentConsumeSchemaMap.put("allOf", requestBodyContentConsumeSchemaAllOfList);
        }
        //设置请求的协议
        requestBodyContentMap.put(consume, requestBodyContentConsumeMap);
        //if (defaultConsume.equalsIgnoreCase(consume)) {//如果是默认的协议,增加一个后台开发模式
        //    requestBodyContentMap.put(devConsume, requestBodyContentConsumeMap);
        //}

    }


    /**
     * 封装请求的返回值,paths-uri-get-responses
     * responses的整体路径是: paths-uri-get-responses-200-content-application/json-schema
     *
     * @param javaMethod                 需要包装的方法
     * @param pathsURIMethodResponsesMap paths-uri-get-responses的Map
     * @param componentsSchemasMap       添加对象属性
     */
    private static void wrapPathsURIMethodResponsesMap(JavaMethod javaMethod, Map<String, Object> pathsURIMethodResponsesMap, Map<String, Object> componentsSchemasMap) {

        //读取java方法参数的注释,@param注解 key是字段名称,value是注释的值,return和throws为key,值是后面的注释
        Map<String, String> methodParamCommentMap = readJavaMethodParamComment(javaMethod);
        String returnComment = methodParamCommentMap.get("return");
        if (StringUtils.isBlank(returnComment)) {
            returnComment = "";
        }

        //paths-uri-get-responses-200
        Map<String, Object> pathsURIMethodResponses200Map = new HashMap<>();
        pathsURIMethodResponses200Map.put("description", returnComment);
        pathsURIMethodResponsesMap.put("200", pathsURIMethodResponses200Map);

        // paths-uri-get-responses-200-content
        Map<String, Object> pathsURIMethodResponses200ContentMap = new HashMap<>();
        pathsURIMethodResponses200Map.put("content", pathsURIMethodResponses200ContentMap);


        //paths-uri-get-responses-200-content-application/json
        Map<String, Object> pathsURIMethodResponses200ContentConsumeMap = new HashMap<>();
        // 目前只支持 json
        pathsURIMethodResponses200ContentMap.put("application/json", pathsURIMethodResponses200ContentConsumeMap);

        //paths-uri-get-responses-200-content-application/json-schema
        Map<String, Object> pathsURIMethodResponses200ContentConsumeSchemaMap = new HashMap<>();
        pathsURIMethodResponses200ContentConsumeMap.put("schema", pathsURIMethodResponses200ContentConsumeSchemaMap);

        //获取返回值类型
        JavaType returnType = javaMethod.getReturnType();

        //包装返回值
        GenericClassUtils.wrapSchemaRefObjectMap(returnType, pathsURIMethodResponses200ContentConsumeSchemaMap, componentsSchemasMap);

    }


    /**
     * 读取java方法参数的注释,@param注解 key是字段名称,value是注释的值,return和throws为key,值是后面的注释
     *
     * @param javaMethod qdox的java方法
     * @return methodParamCommentMap 参数注释的Map
     */
    private static Map<String, String> readJavaMethodParamComment(JavaMethod javaMethod) {
        //参数的中文注释
        List<DocletTag> javaMethodTags = javaMethod.getTags();
        //方法参数的中文注释
        Map<String, String> methodParamCommentMap = new HashMap<>();

        for (DocletTag docletTag : javaMethodTags) {
            String name = docletTag.getName();
            List<String> parameters = docletTag.getParameters();
            String msg = "";

            if (PARAM.equalsIgnoreCase(name)) {//如果是参数
                if (parameters != null && parameters.size() > 1) {
                    for (int i = 1; i < parameters.size(); i++) {
                        msg += parameters.get(i);
                    }
                    methodParamCommentMap.put(parameters.get(0), msg);
                }
            } else if ("return".equalsIgnoreCase(name)) { //返回类型
                methodParamCommentMap.put("return", docletTag.getValue());
            } else if ("throws".equalsIgnoreCase(name)) { //异常
                methodParamCommentMap.put("throws", docletTag.getValue());
            }
        }
        return methodParamCommentMap;
    }


    /**
     * 把用户配置的configMap合并到生成的openAPIMap,用于用户自定义配置
     *
     * @param openAPIMap 生成的openAPIMap
     * @param configMap  用户配置的configMap
     */
    public static void mergeConfigMap(Map openAPIMap, Map configMap) {
        if (openAPIMap == null || configMap == null) {
            return;
        }
        mergeYamlMap(openAPIMap, configMap);

    }


    /**
     * 合并Map
     *
     * @param mainMap
     * @param mergeMap
     */
    private static void mergeYamlMap(Map mainMap, Map mergeMap) {
        if (mainMap == null || mergeMap == null) {
            return;
        }
        for (Object key : mergeMap.keySet()) {
            //Object key=m.getKey();
            Object value = mergeMap.get(key);
            Object mainValue = mainMap.get(key);
            if (value == null) {//如果需要合并的值未null,mainMap删除这个key
                mainMap.remove(key);
                continue;
            } else if (mainValue == null) {//如果mainMap里没有这个key
                mainMap.put(key, value);
                continue;
            }

            if (value instanceof Map) {//如果值是map
                mergeYamlMap((Map) mainValue, (Map) value);
            } else if (value instanceof List) {//如果是List
                mergeYamlList((List) mainValue, (List) value);
            } else {//其他值
                mainMap.put(key, mergeMap.get(key));
            }
        }

    }

    /**
     * 遍历合并List
     *
     * @param mainList
     * @param mergeList
     * @TODO: 2020/8/16
     */
    private static void mergeYamlList(List mainList, List mergeList) {
        //list下的list,Map,值,需要循环合并,
    }


}
